
const vscode = require('vscode');
const path = require('path');
const fs = require('fs');

const defaultAlias = {
  '@/': 'src/'
}

const defaultFixExt = [
  '.js',
  '.ts',
  '.jsx',
  '.vue',
]

function getPathAlias() {
  let pathAlias = defaultAlias
  let customAliasObj = vscode.workspace.getConfiguration('aliasPath')
  if(customAliasObj) {
    let customAliasProxy = customAliasObj.get('alias')
    if(customAliasProxy) {
      let keys = Reflect.ownKeys(customAliasProxy)
      let customAlias = {}
      keys.forEach(k => customAlias[k] = customAliasProxy[k])
      pathAlias = customAlias
    }
  }
  return pathAlias
}

function getPossiblePath(targetAlias, pathAlias, currentFileName, lineText, position) {
  let fullPath = ''
  targetAlias.forEach(key => {
    let text = lineText
    let valuePath = pathAlias[key]
    let leftPathIndex = currentFileName.indexOf(valuePath)
    let leftPath = currentFileName.substring(0, leftPathIndex)
    let pos = position.character;
    let valueTextStart = text.lastIndexOf(`'`, pos)
    valueTextStart = valueTextStart === -1 ? text.lastIndexOf(`"`, pos) : valueTextStart
    let valueTextEnd = text.indexOf(`'`, pos)
    valueTextEnd = valueTextEnd === -1 ? text.indexOf(`"`, pos) : valueTextEnd
    text = text.substring(valueTextStart + 1, valueTextEnd)
    let rightPathIndex = text.indexOf(key)
    let rightPath = text.substring(rightPathIndex + key.length)
    let t = path.join(leftPath, valuePath, rightPath)
    if(/^[0-9A-Za-z_\.\-\\\/]+$/.test(t)) {
      fullPath = t
    }
  })
  return fullPath.replace(/\\/g, '/')
}

function doFixExt(possiblePath) {
  let fixExt = defaultFixExt
  let needFix = true
  fixExt.forEach(o => possiblePath.endsWith(o) && (needFix = false))
  if(needFix) {
    let includeFixExtPath = [possiblePath]
    includeFixExtPath.push(...fixExt.map(o => possiblePath + o))
    let i = possiblePath.lastIndexOf('/')
    let end = possiblePath.substring(i+1)
    let newPath = possiblePath + '/' + end
    includeFixExtPath.push(...fixExt.map(o => newPath + o))
    end = 'index'
    newPath = possiblePath + '/' + end
    includeFixExtPath.push(...fixExt.map(o => newPath + o))
    return includeFixExtPath
  }
  return [possiblePath]
}

function resolvePath(allPossiblePath) {
  return new Promise((resolve) => {
    allPossiblePath.forEach(item => {
      fs.stat(item, (err, stat) => {
        if(!err && stat.isFile()) {
          let pos = new vscode.Location(vscode.Uri.file(item), new vscode.Position(0, 0));
          resolve(pos);
        }
      })
    })
  })
}

function isPath(lineText, position) {
  let text = lineText
  let pos = position.character
  
  let includePathSeparator = text.lastIndexOf(`/`, pos)
  includePathSeparator = includePathSeparator ?? text.lastIndexOf(`/`, pos)

  return includePathSeparator !== -1;
}

function resolveLineText(lineText, position) {
  let text = lineText
  let pos = position.character
  
  if(isPath(lineText, position)) {
    let valueTextStart = text.lastIndexOf(`'`, pos)
    valueTextStart = valueTextStart === -1 ? text.lastIndexOf(`"`, pos) : valueTextStart
    let valueTextEnd = text.indexOf(`'`, pos)
    valueTextEnd = valueTextEnd === -1 ? text.indexOf(`"`, pos) : valueTextEnd
    text = text.substring(valueTextStart + 1, valueTextEnd)
  }

  return text
}

function provideDefinition(document, position, token) {
  let pathAlias = getPathAlias()

  let currentFileName = document.fileName
  let lineText = document.lineAt(position).text

  if(isPath(lineText, position)) {
    let aliasKeys = Object.keys(pathAlias)
    let targetAlias = aliasKeys.filter(o => lineText.indexOf(o) !== -1)
    let possiblePath
    if(targetAlias.length > 0) {
      possiblePath = getPossiblePath(targetAlias, pathAlias, currentFileName, lineText, position)
    } else {
      lineText = resolveLineText(lineText, position)
      possiblePath = path.resolve(currentFileName, '../', lineText)
    }
    let fixExtPath = doFixExt(possiblePath)
    return resolvePath(fixExtPath)
  }
}

module.exports = function(context) {
  let disposable = vscode.languages.registerDefinitionProvider(['javascript', 'vue'], {provideDefinition});

	context.subscriptions.push(disposable);
}